﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLite.Internal;
using NLite.Mini.Context;
using NLite.Reflection;
using NLite.Collections;
using System.Reflection;
using NLite.Mini.Internal;
using NLite.Mini.Listener.Internal;
using NLite.Reflection.Internal;

namespace NLite.Mini.Listener
{
    /// <summary>
    /// 
    /// </summary>
    public sealed class InjectionListener:ComponentListenerAdapter
    {
        private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        private const BindingFlags FieldFlags = BindingFlags.SetField | Flags;
        private const BindingFlags PropertyFlags = BindingFlags.SetProperty | Flags;

        //private IInjectStrategy InjectionStrategy;

        /// <summary>
        /// 
        /// </summary>
        public InjectionListener():base(ComponentListenStage.PostCreation)
        {
            //this.InjectionStrategy = new InjectStrategy();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        public override void OnPostCreation(NLite.Mini.Context.IPostCreationContext ctx)
        {
            if (ctx.Instance == null)
                return;

            const string Key = "InjectionMany";
            Lazy<InjectionInfo[]> lazy = null;
            if (ctx.Component.ExtendedProperties.Contains(Key))
                lazy = ctx.Component.ExtendedProperties[Key] as Lazy<InjectionInfo[]>;

            if (lazy == null)
            {

                lazy = new Lazy<InjectionInfo[]>(() =>
                {
                    var instanceType = ctx.Instance.GetType();
                    return (from f in instanceType.GetFields(FieldFlags)
                            let att = f.GetAttribute<InjectAttribute>(true)
                            let ignoreAtt = f.GetAttribute<IgnoreInjectAttribute>(true)
                            let id = att != null ? att.Id : string.Empty
                            where ignoreAtt == null
                            where att != null
                            select new InjectionInfo
                            {
                                Id = id
                                ,
                                Source = InjectionSource.Container
                                ,
                                Setter = f.ToMemberSetter()
                                ,
                                MemberType = f.FieldType
                            }

                       )
                       .Union(
                       from p in instanceType.GetProperties(PropertyFlags)
                       let ps = p.GetIndexParameters()
                       let attMany = p.GetAttribute<InjectManyAttribute>(true)
                       let att = p.GetAttribute<InjectAttribute>(false)
                       let ignoreAtt = p.GetAttribute<IgnoreInjectAttribute>(false)
                       let id = att != null ? att.Id : string.Empty
                       where ignoreAtt == null || ps == null || ps.Length == 0
                       where att != null
                       select new InjectionInfo
                       {
                           Id = id
                           ,
                           Setter = p.ToMemberSetter()
                           ,
                           MemberType = p.PropertyType
                           ,
                           Source = InjectionSource.Container
                       })
                       .Union(
                       from m in instanceType.GetMethods(Flags)
                       let ps = m.GetParameters()
                       let att = m.GetAttribute<InjectAttribute>(false)
                       let ignoreAtt = m.GetAttribute<IgnoreInjectAttribute>(false)
                       where ignoreAtt == null
                             && m.ReturnType == Types.Void
                             && att != null
                             && ps.TrueForAll(p => !p.HasAttribute<InjectManyAttribute>(false))
                       select new InjectionInfo
                       {
                           Id = att.Id
                           ,
                           Method = DynamicMethodFactory.GetProc(m)
                           ,
                           Parameters = ps
                           ,
                           Source = InjectionSource.Container
                       }
                       ).ToArray();
                });

                ctx.Component.ExtendedProperties[Key] = lazy;
            }

            if (lazy.Value.Length == 0)
                return;

            foreach (var item in lazy.Value)
            {
                if (item.MemberType != null)
                    InjectMember(ctx, item);
                else
                    InjectMemberByMethod(ctx, item);
            }
        }

        private static void InjectMemberByMethod(IPostCreationContext ctx, IInjectionInfo item)
        {
            item.Method(ctx.Instance, ReflectionHelper.GetParameters(ctx.Kernel, item.Parameters));
        }

        private static void InjectMember(IPostCreationContext ctx, IInjectionInfo item)
        {

            if (!MemberMatcher.Match(item.MemberType, ctx.Kernel))
                return;

            item.Setter(ctx.Instance, InjectService.Get(item.Id, item.MemberType, ctx.Kernel, false));
        }
    }
}
